"""
Repository for fingerprint data access and storage.
"""
import logging
from typing import List, Optional, Dict, Any, Tuple
from datetime import datetime, timezone
from motor.motor_asyncio import AsyncIOMotorCollection, AsyncIOMotorDatabase
from pymongo import IndexModel, ASCENDING, DESCENDING
from bson import ObjectId

from src.models.fingerprint import ContentFingerprint, OriginalityReport
from src.algorithms.simhash import SimHash

logger = logging.getLogger(__name__)


class FingerprintRepository:
    """Repository for managing content fingerprints in MongoDB."""
    
    def __init__(self, database: AsyncIOMotorDatabase):
        """
        Initialize repository with MongoDB database.
        
        Args:
            database: AsyncIOMotorDatabase instance
        """
        self.db = database
        self.fingerprints: AsyncIOMotorCollection = database.content_fingerprints
        self.reports: AsyncIOMotorCollection = database.originality_reports
        self.simhash = SimHash()
    
    async def create_indexes(self):
        """Create MongoDB indexes for optimal query performance."""
        # Fingerprint collection indexes
        fingerprint_indexes = [
            IndexModel([("fingerprint", ASCENDING)], name="idx_fingerprint"),
            IndexModel([("content_id", ASCENDING)], name="idx_content_id"),
            IndexModel([("content_type", ASCENDING)], name="idx_content_type"),
            IndexModel([("created_at", DESCENDING)], name="idx_created_at"),
            IndexModel(
                [("fingerprint", ASCENDING), ("content_type", ASCENDING)],
                name="idx_fingerprint_type"
            )
        ]
        
        # Report collection indexes
        report_indexes = [
            IndexModel([("content_id", ASCENDING)], name="idx_report_content_id"),
            IndexModel([("checked_at", DESCENDING)], name="idx_checked_at"),
            IndexModel([("originality_score", ASCENDING)], name="idx_originality_score")
        ]
        
        try:
            await self.fingerprints.create_indexes(fingerprint_indexes)
            await self.reports.create_indexes(report_indexes)
            logger.info("Created database indexes successfully")
        except Exception as e:
            logger.error(f"Error creating indexes: {e}")
            raise
    
    async def save_fingerprint(self, fingerprint: ContentFingerprint) -> str:
        """
        Save a content fingerprint to database.
        
        Args:
            fingerprint: ContentFingerprint instance
            
        Returns:
            Inserted document ID
        """
        try:
            # Check if fingerprint already exists for this content
            existing = await self.fingerprints.find_one({
                "content_id": fingerprint.content_id,
                "content_type": fingerprint.content_type
            })
            
            if existing:
                # Update existing fingerprint
                result = await self.fingerprints.update_one(
                    {"_id": existing["_id"]},
                    {"$set": fingerprint.to_mongo()}
                )
                logger.info(f"Updated fingerprint for content_id: {fingerprint.content_id}")
                return str(existing["_id"])
            else:
                # Insert new fingerprint
                result = await self.fingerprints.insert_one(fingerprint.to_mongo())
                logger.info(f"Saved new fingerprint for content_id: {fingerprint.content_id}")
                return str(result.inserted_id)
                
        except Exception as e:
            logger.error(f"Error saving fingerprint: {e}")
            raise
    
    async def find_similar(
        self,
        fingerprint: str,
        threshold: int = 10,
        content_type: Optional[str] = None,
        limit: int = 50
    ) -> List[Tuple[ContentFingerprint, float]]:
        """
        Find similar content based on fingerprint.
        
        Args:
            fingerprint: SimHash fingerprint to compare
            threshold: Maximum Hamming distance for similarity
            content_type: Optional filter by content type
            limit: Maximum number of results
            
        Returns:
            List of (ContentFingerprint, similarity_score) tuples
        """
        try:
            # Build query
            query = {}
            if content_type:
                query["content_type"] = content_type
            
            # Retrieve candidates (in production, could use LSH for optimization)
            cursor = self.fingerprints.find(query).limit(limit * 2)
            candidates = []
            
            async for doc in cursor:
                fp = ContentFingerprint.from_mongo(doc)
                distance = SimHash.hamming_distance(fingerprint, fp.fingerprint)
                
                if distance <= threshold:
                    similarity = self.simhash.calculate_similarity(fingerprint, fp.fingerprint)
                    candidates.append((fp, similarity))
            
            # Sort by similarity (highest first) and limit results
            candidates.sort(key=lambda x: x[1], reverse=True)
            return candidates[:limit]
            
        except Exception as e:
            logger.error(f"Error finding similar fingerprints: {e}")
            raise
    
    async def batch_compare(
        self,
        fingerprints: List[str],
        threshold: int = 10
    ) -> Dict[str, List[Dict[str, Any]]]:
        """
        Batch compare multiple fingerprints.
        
        Args:
            fingerprints: List of fingerprints to compare
            threshold: Maximum Hamming distance for similarity
            
        Returns:
            Dictionary mapping fingerprints to similar content
        """
        results = {}
        
        try:
            for fp in fingerprints:
                similar = await self.find_similar(fp, threshold)
                results[fp] = [
                    {
                        "content_id": content.content_id,
                        "similarity": similarity,
                        "text_preview": content.text_preview,
                        "source": content.source
                    }
                    for content, similarity in similar
                ]
            
            return results
            
        except Exception as e:
            logger.error(f"Error in batch comparison: {e}")
            raise
    
    async def get_fingerprint_by_content_id(
        self,
        content_id: str,
        content_type: str = "article"
    ) -> Optional[ContentFingerprint]:
        """
        Get fingerprint by content ID.
        
        Args:
            content_id: Content ID to search for
            content_type: Type of content
            
        Returns:
            ContentFingerprint if found, None otherwise
        """
        try:
            doc = await self.fingerprints.find_one({
                "content_id": content_id,
                "content_type": content_type
            })
            
            if doc:
                return ContentFingerprint.from_mongo(doc)
            return None
            
        except Exception as e:
            logger.error(f"Error getting fingerprint: {e}")
            raise
    
    async def delete_fingerprint(self, fingerprint_id: str) -> bool:
        """
        Delete a fingerprint by ID.
        
        Args:
            fingerprint_id: Fingerprint document ID
            
        Returns:
            True if deleted, False otherwise
        """
        try:
            result = await self.fingerprints.delete_one({
                "_id": ObjectId(fingerprint_id)
            })
            return result.deleted_count > 0
            
        except Exception as e:
            logger.error(f"Error deleting fingerprint: {e}")
            raise
    
    async def save_report(self, report: OriginalityReport) -> str:
        """
        Save originality report.
        
        Args:
            report: OriginalityReport instance
            
        Returns:
            Inserted document ID
        """
        try:
            result = await self.reports.insert_one(report.to_mongo())
            logger.info(f"Saved originality report for content_id: {report.content_id}")
            return str(result.inserted_id)
            
        except Exception as e:
            logger.error(f"Error saving report: {e}")
            raise
    
    async def get_report(self, report_id: str) -> Optional[OriginalityReport]:
        """
        Get originality report by ID.
        
        Args:
            report_id: Report document ID
            
        Returns:
            OriginalityReport if found, None otherwise
        """
        try:
            doc = await self.reports.find_one({"_id": ObjectId(report_id)})
            
            if doc:
                return OriginalityReport.from_mongo(doc)
            return None
            
        except Exception as e:
            logger.error(f"Error getting report: {e}")
            raise
    
    async def get_recent_reports(
        self,
        limit: int = 10,
        skip: int = 0
    ) -> List[OriginalityReport]:
        """
        Get recent originality reports.
        
        Args:
            limit: Number of reports to return
            skip: Number of reports to skip
            
        Returns:
            List of OriginalityReport instances
        """
        try:
            cursor = self.reports.find().sort("checked_at", -1).skip(skip).limit(limit)
            reports = []
            
            async for doc in cursor:
                reports.append(OriginalityReport.from_mongo(doc))
            
            return reports
            
        except Exception as e:
            logger.error(f"Error getting recent reports: {e}")
            raise
    
    async def cleanup_old_fingerprints(self, days: int = 90) -> int:
        """
        Clean up fingerprints older than specified days.
        
        Args:
            days: Number of days to keep fingerprints
            
        Returns:
            Number of deleted documents
        """
        try:
            cutoff_date = datetime.now(timezone.utc)
            cutoff_date = cutoff_date.replace(day=cutoff_date.day - days)
            
            result = await self.fingerprints.delete_many({
                "created_at": {"$lt": cutoff_date}
            })
            
            logger.info(f"Cleaned up {result.deleted_count} old fingerprints")
            return result.deleted_count
            
        except Exception as e:
            logger.error(f"Error cleaning up fingerprints: {e}")
            raise